21.05.2020

https://leetcode.com/problems/decode-ways/

How to solve

To make things simpler, let's start with the case where s is a string of length 1. There is only one way to decode s, which is to interpret it as a single character if s[0] is between 1 and 9 inclusive. We can't decode s if s[0] is 0 because letters are mapped starting from A -> 1 to Z -> 26. Hence, there is at most 1 way to decode s.

When s is of length 2, we can decode each character individually the same way as we did when s was of length 1. Also, we can check if decoding both numbers together yields a valid letter. Specifically, if the integer formed by concatenating s[0] and s[1] is between 10 and 26 inclusive, we can decode them as a single letter. Hence, there are at most 2 ways to decode s.

When s is of length 3, we repeat the above steps to decode s. Note that we aren't adding any new ways to decode s if we decode each integer in s as individual letters. Suppose we check if we can decode s[1]s[2] as a single letter. If the sum can be intepreted as a single letter, then we need to know how many ways there are to interpret the first integer s[0] (could be 0 or 1). Hence, there are at most 3 ways to decode s.

When s is of length 4, we aren't adding any new ways to decode s if we interpret each individual digit as a letter. But if we interpret s[2]s[3] as a single letter, we need to know how many ways there are to intepret s up to index 1 (could be 0, 1, or 2).

Hence, we get the recurrence,

# of ways to decode up to index i = # of ways to decode up to i - 1 + # of ways to decode up to i - 2

This relation should seem familiar. It's the fibonnaci sequence, but with some extra conditions we have to check.

First, if s[i] is the digit 0, s[i] doesn't map to a letter. So the # of ways to decode s at index i is currently 0.

Second, if s[i-1]s[i] is not an integer between 10 and 26 inclusive, both digits cannot be decoded as a single letter. So we don't add any additional ways to decode s at index i.

Complexity Analysis

Time: O(N)

We iterate through all digits in s once.

Space: O(N)

We need to remember the number of ways to decode s at each index. We create a dp array with the same length as s.

Solutions

Python

def numDecodings(self, s: str) -> int:
    if not s:
        return 0

    num_decode = [0 for _ in range(len(s))]

    if s[0] != "0":
        num_decode[0] = 1

    for i in range(1, len(s)):
        if s[i] != "0":
            num_decode[i] = num_decode[i - 1]

        if 10 <= int(s[i - 1 : i + 1]) <= 26:
            if i == 1:
                num_decode[i] += 1
            else:
                num_decode[i] += num_decode[i - 2]

    return num_decode[-1]

Go

func numDecodings(s string) int {
    if len(s) == 0 {
        return 0
    }

    num_decode := make([]int, len(s))

    if s[0] != '0' {
        num_decode[0] = 1
    }

    for i := 1; i < len(s); i++ {
        if s[i] != '0' {
            num_decode[i] = num_decode[i - 1]
        }

        num, _ := strconv.Atoi(s[i - 1 : i + 1])
        if num >= 10 && num <= 26 {
            if i == 1 {
                num_decode[i] += 1
            } else {
                num_decode[i] += num_decode[i - 2]
            }
        }
    }

    return num_decode[len(s) - 1]
}

Rust

pub fn num_decodings(s: String) -> i32 {
    if s.len() == 0 {
        return 0;
    }

    let mut dp = vec![0; s.len()];
    let b = s.as_bytes();  // have to convert s to byte array because rust doesn't support char
                            // indexing

    if b[0] != b'0' {
        dp[0] = 1;
    }
  
    for i in 1..s.len() {
        if b[i] != b'0' {
            dp[i] = dp[i - 1];
        }

        let num = (b[i - 1] - b'0') * 10 + (b[i] - b'0');
        if num >= 10 && num <= 26 {
            if i == 1 {
                dp[i] += 1;
            } else {
                dp[i] += dp[i - 2]
            }
        }
    }

    dp[s.len() - 1]
}